﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace Mbs.Threading
{
    public static class Paraller
    {
        private static readonly int ProcessorCount = Environment.ProcessorCount;
        private static readonly int ThreadsCount;

        [DebuggerDisplay("ThreadIndex={ThreadIndex}, From={From},To={To}")]
        struct TaskItem
        {
            public Barrier Barrier;
            public int ThreadIndex;
            public int From;
            public int To;
            public Action<int> Body;

            public void Run()
            {
                var m = From;
                var n = To;

                for (; m <= n; m++)
                    Body(m);

                Barrier.Await();
            }

        }

        static Paraller()
        {
            ThreadsCount = ProcessorCount * 2;
        }

        public static void For<T>(T[] array, Action<T> body)
        {
            if (array == null)
                return;
            if (body == null)
                return;

            For(0, array.Length, i => body(array[i]));
        }

        public static void For(int fromInclusive, int toExclusive, Action<int> body)
        {
            var length = toExclusive - fromInclusive;
            if (length == 0)
                return;
            if (body == null)
                return;

            if (ThreadsCount == 1)
            {
                for (int i = fromInclusive; i < toExclusive; i++)
                    body(i);
                return;
            }

            int chunk = length / ThreadsCount;
            var tasks = AllocateTasks(body, length, chunk);
            RunTasksByThreadPool(tasks);
            //RunTasksByThread(tasks);
        }
      
        private static void RunTasksByThreadPool(TaskItem[] tasks)
        {
            for (int theadIndex = 0; theadIndex < ThreadsCount - 1; theadIndex++)
            {
                var i = theadIndex;
                ThreadPool.QueueUserWorkItem(o => tasks[i].Run());
            }

            tasks[ThreadsCount - 1].Run();
           
        }

        private static void RunTasksByThread(TaskItem[] tasks)
        {

            for (int theadIndex = 0; theadIndex < ThreadsCount - 1; theadIndex++)
            {
                var i = theadIndex;
                new Thread( o=> tasks[i].Run()){ IsBackground = true }.Start();
            }

            tasks[ThreadsCount - 1].Run();
        }

        private static TaskItem[] AllocateTasks(Action<int> body, int length, int chunk)
        {
            Barrier barrier = new Barrier(ThreadsCount);
            var tasks = new TaskItem[ThreadsCount];
            int remain = length;

            for (int i = 0; i < ThreadsCount; i++)
            {
                var taskItems = new TaskItem { ThreadIndex = i + 1, Barrier = barrier, Body = body, };
                taskItems.From = i * chunk;
                if (i != ThreadsCount - 1)
                {
                    remain -= chunk;
                    taskItems.To = taskItems.From + chunk - 1;
                }
                else
                    taskItems.To = taskItems.From + remain - 1;

                tasks[i] = taskItems;
            }
            return tasks;
        }

        public static void Foreach<T>(IEnumerable<T> array, Action<T> body)
        {
            if (array == null)
                return;

            For<T>(array.ToArray(), body);
        }
    }
}


